package cn.zdsoft.configuration.core.web;

import cn.zdsoft.configuration.core.AbstractCallbackAble;
import cn.zdsoft.configuration.core.Constants;
import cn.zdsoft.configuration.core.exceptions.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * spring 回调处理
 * Created by wangdongdong on 2018/5/16 10:30.
 */
public class SpringCallbackAble extends AbstractCallbackAble {

    private static final Logger logger = LoggerFactory.getLogger(SpringCallbackAble.class);

    private ApplicationContext applicationContext;

    @Override
    public boolean doOnChange(String key, String originValue, String newValue, Properties properties) throws ConfigurationException {
        Object o = getConfigContext().getAttribute(Constants.APPLICATION_CONTEXT);
        if (o == null) {
            throw new IllegalStateException();
        }
        this.applicationContext = (ApplicationContext) o;
        logger.info("======================================================================");
        logger.info("spring call back");
        logger.info("======================================================================");
        // update xml bean value
        reloadXmlBean(key, newValue);
        // update used @Value field value
        reloadAnnoValue(key, newValue);
        return true;
    }

    /**
     *
     * 配置文件bean变量重新取值
     *
     * @param key
     * @param newValue
     * @throws ConfigurationException
     */
    private void reloadXmlBean(String key, String newValue) throws ConfigurationException {
        Object attribute = getConfigContext().getAttribute(Constants.BEAN_PROPERTY_MAP);
        if (attribute == null) {
            throw new IllegalStateException();
        }
        Map<String, List<AbstractPropertyPlaceholderConfigurer.PropertyInfo>> propertyInfoMap = (Map<String, List<AbstractPropertyPlaceholderConfigurer.PropertyInfo>>) attribute;
        for (Map.Entry<String, List<AbstractPropertyPlaceholderConfigurer.PropertyInfo>> entry : propertyInfoMap.entrySet()) {
            String beanName = entry.getKey();
            for (AbstractPropertyPlaceholderConfigurer.PropertyInfo propertyInfo : entry.getValue()) {
                String fieldName = propertyInfo.getFieldName();
                String keyName = propertyInfo.getKeyName();
                if (key.equals(keyName)) {
                    Object bean = applicationContext.getBean(beanName);
                    setField(bean, fieldName, newValue);
                }
            }
        }
    }

    /**
     *
     * @Value注解重新取值
     *
     * @param key
     * @param newValue
     * @throws ConfigurationException
     */
    private void reloadAnnoValue(String key, String newValue) throws ConfigurationException {
        String[] beanNames = applicationContext.getBeanNamesForAnnotation(Controller.class);
        for (String beanName : beanNames) {
            Object bean = applicationContext.getBean(beanName);
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field f : fields) {
                Value valAnno = f.getAnnotation(Value.class);
                if (valAnno != null) {
                    String valKey = valAnno.value().substring(valAnno.value().lastIndexOf("{") + 1, valAnno.value().lastIndexOf("}"));
                    if (key.equals(valKey)) {
                        setField(bean, f, newValue);
                    }
                }
            }
        }
    }

    private void setField(Object bean, Field field, String fieldValue) throws ConfigurationException {
        Class<?> clazz = bean.getClass();
        String setter = getSetterMethod(field.getName());
        field.setAccessible(true);
        try {
            if (field.getType() == Integer.class) {

                Method method = clazz.getDeclaredMethod(setter, Integer.class);
                method.invoke(bean, Integer.valueOf(fieldValue));
            } else if (field.getType() == Long.class) {

                Method method = clazz.getDeclaredMethod(setter, Long.class);
                method.invoke(bean, Long.valueOf(fieldValue));
            } else if (field.getType() == Double.class) {

                Method method = clazz.getDeclaredMethod(setter, Double.class);
                method.invoke(bean, Double.valueOf(fieldValue));
            } else if (field.getType() == Float.class) {

                Method method = clazz.getDeclaredMethod(setter, Float.class);
                method.invoke(bean, Float.valueOf(fieldValue));
            } else if (field.getType() == Short.class) {

                Method method = clazz.getDeclaredMethod(setter, Short.class);
                method.invoke(bean, Short.valueOf(fieldValue));
            } else if (field.getType() == Byte.class) {

                Method method = clazz.getDeclaredMethod(setter, Byte.class);
                method.invoke(bean, Byte.valueOf(fieldValue));
            } else if (field.getType() == Boolean.class) {

                Method method = clazz.getDeclaredMethod(setter, Boolean.class);
                method.invoke(bean, Boolean.valueOf(fieldValue));
            } else {

                Method method = clazz.getDeclaredMethod(setter, String.class);
                method.invoke(bean, fieldValue);
            }
        } catch (IllegalAccessException e) {
            throw ConfigurationException.createExceptin(MessageFormat.format("error when set property [{}] value [{}]", field.getName(), fieldValue), e);
        } catch (NoSuchMethodException e) {
            throw ConfigurationException.createExceptin(MessageFormat.format("no such method '{}'", setter), e);
        } catch (InvocationTargetException e) {
            throw ConfigurationException.createExceptin(MessageFormat.format("no such method '{}'", setter), e);
        }
    }

    private void setField(Object bean, String fieldName, String fieldValue) throws ConfigurationException {
        try {
            Field field = bean.getClass().getDeclaredField(fieldName);
            setField(bean, field, fieldValue);
        } catch (NoSuchFieldException e) {
            throw ConfigurationException.createExceptin(MessageFormat.format("error when set property [{}] value [{}], no field named {}", fieldName, fieldValue, fieldName));
        }
    }

    private String getSetterMethod(String fieldName) {

        return "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, fieldName.length());
    }
}
